home *** CD-ROM | disk | FTP | other *** search
- A Callback Class
-
- Doug Young
-
-
-
-
-
-
-
-
-
- This paper briefly describes an alternate approach for using C++ member
- functions with Xt callbacks. This material was originally written as an
- appendix to Object-Oriented Programming with C++ and OSF/Motif f,
- Prentice Hall, 1992, but was cut from the final manuscript in the interest of
- simplicity and consistency.
-
-
-
-
-
-
-
- The approach used throughout Object-Oriented Programming with C++ and OSF/Motif for dealing
- with member functions and widget callbacks requires two functions. One function is declared as a
- static member function and registered as a Motif callback using XtAddCallback(). An object's
- this pointer is provided as client data when the callback is registered. When this static member
- function is called, it retrieves the instance pointer from its client data argument and calls the second
- function, which can be a normal member function, relative to that object.
-
- This approach leads to many member functions that are very similar. For example, almost every
- callback function looks like:
-
- void SomeClass::memberFunctionCallback ( Widget,
-
- XtPointer clientData,
-
- XtPointer )
-
- {
-
- SomeClass *obj = (SomeClass *) clientData;
-
- obj->memberFunction();
-
- }
-
-
-
- The only difference between various static functions used as callbacks is the class to which the
- function belongs, and the exact member function being called. The form is identical in all cases
-
- Because the form of all these functions is so similar, it is possible to hide some of the details of
- this approach. One way to do this is to capture the entire mechanism in a C++ class. This paper
- examines an implementation of such a class, the Callback class.
-
- The Callback class has some similarities to the Cmd class described in Chapter 7 of Object-
- Oriented Programming with C++ and OSF/Motif. However, the implementation is a bit different.
- Basically, every class that wants to use a Callback object must create a new derived class that
- overrides an execute() member function declared by the Callback class. This member function
- is called by a static member function, registered by the Callback base class as the actual callback
- function. The execute() function is expected to call a normal member function.
-
- This scheme requires only a single static member function per class, and only a single
- execute() member function. The technique allows normal member functions to be registered as
- pseudo-callbacks. However, the approach adds one more level of indirection than the alternate
- approach.
-
- Let's start by looking at the declaration of the Callback class. The entire mechanism is imple-
- mented in a header file, using macros and inline functions. The file Callback.h is written as follows:
-
- 1 ////////////////////////////////////////////////////////
-
- 2 // Callback.h: A class that encapsulates callbacks
-
- 3 ////////////////////////////////////////////////////////
-
- 4 #ifndef CALLBACK_H
-
- 5 #define CALLBACK_H
-
- 6
-
- 7 #include <Xm/Xm.h>
-
- 8 #include <generic.h>
-
- 9
-
- 10 class Callback {
-
- 11
-
- 12 protected:
-
- 13
-
- 14 Widget _w; // Widget for which callback is registered
-
- 15 String _name; // The callback name,
-
- 16 // needed to remove callback
-
- 17 XtPointer _clientData; // Xt's idea of client data is used
-
- 18 // for the this pointer,
-
- 19 // so replace it with our own.
-
- 20
-
- 21 // Constructor registers callback and saves other info
-
- 22 // that might be needed later as data members
-
- 23
-
- 24 Callback ( Widget w, String name, XtPointer clientData )
-
- 25 {
-
- 26 XtAddCallback ( w,
-
- 27 name,
-
- 28 &Callback::callbackStub,
-
- 29 (XtPointer) this );
-
- 30 _w = w;
-
- 31 _name = name;
-
- 32 _clientData = clientData;
-
- 33 }
-
- 34
-
- 35 // Destructor removes the callback, using save data members
-
- 36
-
- 37 ~Callback()
-
- 38 {
-
- 39 XtRemoveCallback ( _w,
-
- 40 _name,
-
- 41 &Callback::callbackStub,
-
- 42 (XtPointer)this );
-
- 43 }
-
- 44
-
- 45 // Execute must be overriden by derived classes
-
- 46
-
- 47 virtual void execute ( Widget, XtPointer, XtPointer ) = 0;
-
- 48
-
- 49 private:
-
- 50
-
- 51 // Generic callback function works for all callbacks
-
- 52
-
- 53 static void callbackStub ( Widget w,
-
- 54 XtPointer clientData,
-
- 55 XtPointer callData )
-
- 56 {
-
- 57 Callback *obj = (Callback *) clientData;
-
- 58 obj->execute ( w, obj->_clientData, callData );
-
- 59 }
-
- 60 };
-
- The Callback class declares three protected data members, the widget with which the callback
- is registered, the name of the callback, and a pointer that can be used to point to some arbitrary data.
- Recall that the static member function scheme uses the client data supported by
- XtAddCallback() to pass the this pointer to the callback function. Although this scheme still
- needs to have the this pointer, wrapping the callback in a class hides the process and provides a
- place to keep some additional data on a per callback basis. This allows us to make the client data
- argument available to programmers again.
-
- The Callback constructor expects a widget, the name of a callback and a pointer to any client
- data. It registers callbackStub(), which is a static member function defined by the Callback
- class, as the named callback function for the given widget. The Callback constructor also retains the
- widget, the name, and the client data as data members in the Callback object.
-
- The destructor is very simple. It uses the callback name and widget kept in the object and calls
- XtRemoveCallback() to remove the callback registered in the constructor.
-
- The function callbackStub() is registered for all callbacks, regardless of the type. It is
- declared as an inline static member function, and looks much like the other callback function used
- throughout this book. It retrieves the this pointer provided as client data when the callback was
- registered, casts it to the appropriate type, and calls the execute() member function for this
- instance. Because all callbacks use this same member function, the type of the object passed as client
- data will always be Callback. This makes this approach slightly more type safe than the alternate
- approach, in spite of the cast, which is still needed.
-
- The execute() member function is declared as a pure virtual function, and must therefore be
- defined by derived classes. This function calls the member function registered as the pseudo-
- callback.
-
- Now lets' take a first look at how this class is used. The Callback class cannot be used directly.
- A derived class must be created from every class that needs to register callbacks. It is easiest to just
- look at an example. The following ControlCallback class could be used with the Control class
- described in Chapter 2 of Object-Oriented Programming with C++ and OSF/Motif. We could
- declare the ControlCallback class as follows:
-
- /////////////////////////////////////////////////////////////
-
- // ControlCallback.h: Callback class to be used with Control
-
- //////////////////////////////////////////////////////////////
-
- #ifndef CONTROLCALLBACK_H
-
- #define CONTROLCALLBACK_H
-
-
-
- #include "Callback.h"
-
-
-
- // Define ControllPMF as a type
-
-
-
- typedef void ( Control::*ControlPMF )( Widget, XtPointer, XtPointer );
-
-
-
- // Declare the ControlCallback class as a derived calss of Callback
-
-
-
- class ControlCallback : public Callback {
-
-
-
- public:
-
-
-
- // Constructor takes a pointer to an instance of Control,
-
- // and a pointer to a Control member function, in addition
-
- // to the arguments requires by the Callback constructor
-
-
-
- ControlCallback ( Control *obj,
-
- ControlPMF pmf,
-
- Widget w,
-
- String callbackName,
-
- XtPointer clientData ) :
-
- Callback ( w, callbackName, clientData )
-
- {
-
- _obj = obj; // Keep the instance pointer around
-
- _pmf = pmf; // Remember the member function pointer
-
- }
-
-
-
- protected:
-
-
-
- Control *_obj; // Stores a Control object
-
- ControlPMF _pmf; // A pointer to a Control member function
-
-
-
- private:
-
-
-
- // execute() is called from Callback class, indirectly
-
- // when callback is invoked. Use the stored Control instance
-
- // to call the member function. Pass the widget, calldata
-
- // and client data, as stored in this object.
-
-
-
- virtual void execute ( Widget w,
-
- XtPointer clientData,
-
- XtPointer callData )
-
- {
-
- ( _obj->*_pmf )( w, clientData, callData );
-
- }
-
- };
-
-
-
- To use this class, we need to instantiate a ControlCallback object with the appropriate arguments
- for each desired callback. Notice that only one ControlCallback class is needed regardless of how
- many widgets or how many different member functions are supported by the Control class.
-
- However, notice that this ControlCallback class can only be used with the Control class. Using
- this technique with other classes would require a new derived class, with the appropriate members
- defined to point to instances of each new class.
-
- This seems like a lot of work, just to avoid writing a simple static member function. Fortunately,
- we can use a simple technique to reduce the amount of work require down to a single line of code.
- Let's return to the file Callback.h and look at the following macro, which is defined following the
- Callback class declaration:
-
- 61 #define Callbackdeclare(CLASS) \
-
- 62 class CLASS; \
-
- 63 \
-
- 64 typedef void (CLASS::*name2(CLASS,PMF))( Widget, \
-
- 65 XtPointer, \
-
- 66 XtPointer ); \
-
- 67 \
-
- 68 class name2(CLASS,Callback) : public Callback { \
-
- 69 \
-
- 70 public: \
-
- 71 \
-
- 72 name2(CLASS,Callback)( CLASS* obj, \
-
- 73 name2(CLASS,PMF) pmf, \
-
- 74 Widget w, \
-
- 75 String callbackName, \
-
- 76 XtPointer clientData ) : \
-
- 77 Callback ( w, \
-
- 78 callbackName, \
-
- 79 clientData ) \
-
- 80 { \
-
- 81 _obj = obj; \
-
- 82 _pmf = pmf; \
-
- 83 } \
-
- 84 \
-
- 85 protected: \
-
- 86 \
-
- 87 CLASS* _obj; \
-
- 88 name2(CLASS,PMF) _pmf; \
-
- 89 \
-
- 90 private: \
-
- 91 \
-
- 92 virtual void execute ( Widget w, \
-
- 93 XtPointer clientData, \
-
- 94 XtPointer callData ) \
-
- 95 { \
-
- 96 ( _obj->*_pmf )( w, clientData, callData ); \
-
- 97 } \
-
- 98 }; \
-
- 99 \
-
- 100 name2(CLASS,Callback) *addCallback ( Widget w, \
-
- 101 String name, \
-
- 102 name2(CLASS,PMF) pmf, \
-
- 103 XtPointer clientData ) \
-
- 104 { \
-
- 105 name2(CLASS,Callback) *cb = \
-
- 106 new name2(CLASS,Callback)( this, \
-
- 107 pmf, \
-
- 108 w, \
-
- 109 name, \
-
- 110 clientData ); \
-
- 111 return cb; \
-
- 112 }
-
- 113 #endif
-
- This macro captures the format of the ControlCallback class we just discussed. This macro uses
- the name2() macro found in the file generic.h to concatenate two symbols together to generate one
- new symbol. If we were to process the statement
-
- CallbackDeclare(Control)
-
- through the C++ preprocessor, the result would be approximately the same as the ControlCallback
- class implemented earlier. There is one additional feature of this macro, the addCallback()
- function defined starting on line 100. This is a convenience function that instantiates a Callback
- object and provides the this pointer to the constructor.
-
- This macro is intended to be included in the declaration of a class. This makes the
- addCallback() function a member function of the declaring class.
-
- With this macro defined, it is very easy to use the Callback class. Let's look at how it could be
- used with the Control class described in Chapter 2 of Object-Oriented Programming with C++ and
- OSF/Motif. The class declaration is nearly identical to the declaration in that book, except that the
- class has no static member functions, and the Callbackdeclare macro is included inside the
- private portion of the class. Notice that the stop() and start() member functions support the
- arguments expected by the Callback class as well.
-
- 1 ////////////////////////////////////////////////////////////////////
-
- 2 // Control.h: A start/stop pair of buttons for the stopwatch program
-
- 3 ////////////////////////////////////////////////////////////////////
-
- 4 #ifndef CONTROL_H
-
- 5 #define CONTROL_H
-
- 6 #include "BasicComponent.h"
-
- 7 #include "Callback.h"
-
- 8
-
- 9 class Timer;
-
- 10 class Stopwatch;
-
- 11
-
- 12 class Control : public BasicComponent {
-
- 13
-
- 14 private:
-
- 15
-
- 16 void start ( Widget, XtPointer, XtPointer );
-
- 17 void stop ( Widget, XtPointer, XtPointer );
-
- 18
-
- 19 Callbackdeclare(Control)
-
- 20
-
- 21 protected:
-
- 22
-
- 23 Timer *_timer; // The timer controlled by this class
-
- 24 Stopwatch *_stopwatch; // The stopwatch containing this control
-
- 25 Widget _startWidget; // The start button
-
- 26 Widget _stopWidget; // Stop button
-
- 27
-
- 28 public:
-
- 29
-
- 30 Control ( Widget, char * , Stopwatch *, Timer * );
-
- 31 };
-
- 32 #endif
-
- The Control constructor is much the same as before. However, this version registers normal
- member functions as callbacks using the addCallback() member function generated by the
- Callbackdeclare() macro. Notice that the client data is specified as NULL in this case, and
- that no this pointer is required.
-
- 1 //////////////////////////////////////////////////////////////////
-
- 2 // Control.C: A start/stop pair of buttons for the stopwatch program
-
- 3 ///////////////////////////////////////////////////////////////////
-
- 4 #include "Control.h"
-
- 5 #include <Xm/Xm.h>
-
- 6 #include <Xm/RowColumn.h>
-
- 7 #include <Xm/PushB.h>
-
- 8 #include "Timer.h"
-
- 9 #include "Stopwatch.h"
-
- 10
-
- 11 Control::Control ( Widget parent,
-
- 12 char *name,
-
- 13 Stopwatch *stopwatch,
-
- 14 Timer *timer ) : BasicComponent ( name )
-
- 15 {
-
- 16
-
- 17 _timer = timer; // Keep a pointer to the timer.
-
- 18
-
- 19 _stopwatch = stopwatch;
-
- 20
-
- 21 // Create the component's widget tree
-
- 22
-
- 23 _w = XmCreateRowColumn ( parent, _name, NULL, 0 );
-
- 24
-
- 25 _startWidget = XtCreateManagedWidget ( "start",
-
- 26 xmPushButtonWidgetClass,
-
- 27 _w, NULL, 0 );
-
- 28 _stopWidget = XtCreateManagedWidget ( "stop",
-
- 29 xmPushButtonWidgetClass,
-
- 30 _w, NULL, 0 );
-
- 31
-
- 32 // Register callbacks, specifying the object's instance
-
- 33 // pointer as client data.
-
- 34
-
- 35 addCallback ( _startWidget,
-
- 36 XmNactivateCallback,
-
- 37 &Control::start,
-
- 38 NULL );
-
- 39
-
- 40 addCallback ( _stopWidget,
-
- 41 XmNactivateCallback,
-
- 42 &Control::stopCallback,
-
- 43 NULL );
-
- 44
-
- 45 }
-
- The stop() and start() member functions are written the same as they were previously,
- except that the functions take additional arguments, which are unused in this example.
-
- 46 void Control::start( Widget, XtPointer, XtPointer )
-
- 47 {
-
- 48 _timer->start();
-
- 49 _stopwatch->timerStarted();
-
- 50 }
-
- 51 void Control::stop ( Widget, XtPointer, XtPointer )
-
- 52 {
-
- 53 _timer->stop();
-
- 54 _stopwatch->timerStopped();
-
- 55 }
-
- This approach offers several advantage. It is simpler to use, given the complete implementation
- of the Callback class and the Callbackdeclare macro. It is also slightly more type-safe. The
- only cast occurs in association with the clientData argument to the callbackStub()
- function. However, this is known to be a Callback object, regardless of what derived class is actually
- being used, making this cast almost completely safe.
-
- Using the Callbackdeclare macro reduces the number of functions a programmer must
- write and relieves the tedium of defining two functions for every callback. The total number of
- functions is also reduced, since the same static member function is used for all callbacks registered
- by a given class. Finally, some may find this technique more aesthetically appealing than registering
- static member functions and dealing with the C functions directly.
-
- Nothing comes for free, and there are a few disadvantages to this approach. First, the Callback
- mechanism adds yet one more level of indirection, requiring an additional function call for each
- callback. This is probably not objectionable in most cases.
-
- A second problem could be more problematic in some cases. When a widget is destroyed, Xt
- removes all callbacks from the widget's callback lists. When using this approach, Xt will also
- remove the callbackStub() function registered with the widget. However, it cannot know to
- delete the Callback object. If widgets are destroyed dynamically, the implementation described
- above will continue to use memory even though the functions supported by the class will never be
- called.
-
- It is possible to work around this problem by registering an XmNdestroyCallback function
- for each widget and each callback class, whose task it is to free the Callback class when the widget
- is destroyed. However, this adds even more to the complexity and overhead of the underlying
- mechanisms that implement this approach.
-
- I would like to credit Anil Pal for suggesting this approach, and demonstrating an earlier
- version, from which the Callback class evolved.
-
-
-